/*
 * Class
 * Normal Distribution
 */

package pl.abstractvoid.distributions;

import java.util.Random;
import java.util.TreeMap;
import pl.abstractvoid.datamodel.parameters.ParameterSet;
import pl.abstractvoid.datamodel.parameters.SingleParameter;
import pl.abstractvoid.datamodel.parameters.exceptions.NoSuchParameterException;
import pl.abstractvoid.distributions.exceptions.ParameterValidationFailException;
import pl.abstractvoid.rconnector.RCallerInstance;
import pl.abstractvoid.rconnector.RInterpreter;
import rcaller.exception.ParseException;
 
/**
 * Represents normal (Gaussian) probability distribution.
 * 
 * @author Wojciech Szałapski
 */
public class NormalDistribution extends AbstractDistribution {    

    /**
     * String representing the name of the variable which stores the expected
     * value in R.
     */
    private final static String expectedValueString = "expectedValue";
    /**
     * String representing the name of the variable which stores the standard
     * deviation in R.
     */
    private final static String standardDeviationString = "standardDeviation";
    /**
     * Names for output values used by R interpreter.
     */
    private final static String[] outputTableNames;
    /**
     * Names for output values used by user interface.
     */
    private final static String[] outputTableVisibleNames;
    /**
     * Buffer for the script executed when the output is computed.
     */
    private final static StringBuilder normalSetUpOutputScript = new StringBuilder();
    /**
     * Buffer for the script executed when the cumulative distribution plot is
     * generated.
     */
    private final static StringBuilder normalSetUpCumulativeDistributionPlotScript = new StringBuilder();
    /**
     * Buffer for the script executed when the probability density/mass function 
     * plot is generated.
     */
    private final static StringBuilder normalSetUpProbabilityPlotScript = new StringBuilder();
    
    /*
     * Initialization of:
     *  - Names of output parameters.
     *  - Script for computing output parameters.
     *  - Scripts for generating plots.
     */
    static {
        final int numberOfParameters = 1;
        outputTableNames = new String[numberOfParameters];
        outputTableVisibleNames = new String[numberOfParameters];
        outputTableNames[0] = "variance";
        
        normalSetUpOutputScript.append(outputTableNames[0]).append(" = ").
                append(RInterpreter.inputRList).append("$").append(standardDeviationString).
                append("^2\n");
        normalSetUpOutputScript.append(RInterpreter.outputRList).append(" = list(");
        String prefix = "";
        for (String name : outputTableNames) {
            normalSetUpOutputScript.append(prefix).append(name).append(" = ").append(name);
            prefix = ", ";
        }
        normalSetUpOutputScript.append(")");
        
        final int plotPrecision = 5000;
        normalSetUpCumulativeDistributionPlotScript.append(
                "xMinimumValue = qnorm(0.0001, ").
                append(RInterpreter.inputRList).append("$").append(expectedValueString).append(", ").
                append(RInterpreter.inputRList).append("$").append(standardDeviationString).append(")\n").
                append("xMaximumValue = qnorm(0.9999, ").
                append(RInterpreter.inputRList).append("$").append(expectedValueString).append(", ").
                append(RInterpreter.inputRList).append("$").append(standardDeviationString).append(")\n").
                append("plotX = seq(xMinimumValue, xMaximumValue, "
                + "length.out = ").append(plotPrecision).append(")\n"
                + "normalPlot(plotX, pnorm(plotX, ").
                append(RInterpreter.inputRList).append("$").append(expectedValueString).append(", ").
                append(RInterpreter.inputRList).append("$").append(standardDeviationString).append("), "
                + "type = 'l')");
        
        normalSetUpProbabilityPlotScript.append(normalSetUpCumulativeDistributionPlotScript.
                toString().replaceAll("pnorm", "dnorm"));
    }

    /**
     * Constructs the distribution using given input parameters.
     * 
     * @param expectedValue Expected value, i. e. mean.
     * @param standardDeviation Standard deviation (square root of variance).
     * @throws ParameterValidationFailException
     * @throws NoSuchParameterException 
     */
    public NormalDistribution(double expectedValue, double standardDeviation)
            throws ParameterValidationFailException, NoSuchParameterException {
        super(normalSetUpOutputScript, normalSetUpCumulativeDistributionPlotScript, 
              normalSetUpProbabilityPlotScript);
        distributionName = "normalDistribution";
        rState = new RCallerInstance();
        SingleParameter expectedValueParam = new SingleParameter(expectedValueString);
        expectedValueParam.setParameterValue(expectedValue);
        SingleParameter standardDeviationParam = new SingleParameter(standardDeviationString);
        standardDeviationParam.setParameterValue(standardDeviation);
        TreeMap<String, SingleParameter> singleParameters = new TreeMap<>();
        singleParameters.put(expectedValueString, expectedValueParam);
        singleParameters.put(standardDeviationString, standardDeviationParam);
        ParameterSet parameterSet = new ParameterSet();
        parameterSet.setSingleParameters(singleParameters);
        updateInput(parameterSet);
    }

    @Override
    protected void validate(ParameterSet parameters) throws ParameterValidationFailException {
        TreeMap<String, SingleParameter> singleParameters = parameters.getSingleParameters();
        SingleParameter standardDeviationParam = singleParameters.get(standardDeviationString);
        if (standardDeviationParam.getParameterValue() < 0) {
            throw new ParameterValidationFailException("invalidStandardDeviation");
        }
    }
    
    @Override
    public ParameterSet loadParameters() throws NoSuchParameterException, ParseException {
        rState.runCustomCode("", RInterpreter.inputRList);
        SingleParameter expectedValueParam = new SingleParameter(expectedValueString);
        expectedValueParam.setParameterValue(getSingleParameterValue(expectedValueString));
        SingleParameter standardDeviationParam = new SingleParameter(standardDeviationString);
        standardDeviationParam.setParameterValue(getSingleParameterValue(standardDeviationString));
        TreeMap<String, SingleParameter> singleParameters = new TreeMap<>();
        singleParameters.put(expectedValueString, expectedValueParam);
        singleParameters.put(standardDeviationString, standardDeviationParam);
        ParameterSet result = new ParameterSet();
        result.setSingleParameters(singleParameters);
        return result;
    }

    @Override
    protected void initializeOutputTable() {
        outputTable = new SingleParameter[outputTableNames.length];
        for (int i = 0; i < outputTableNames.length; ++i) {
            outputTable[i] = new SingleParameter(outputTableNames[i]);
        }
    }
    
    /**
     * Returns a new instance of a normal distribution with random expected
     * value (from -10 to 10) and random standard deviation (from 0 to 5).
     * 
     * @return New instance of normal distribution with random parameters.
     */
    public static NormalDistribution getSampleDistributionData() {
        Random generator = new Random();
        double expectedValue = generator.nextDouble() * (generator.nextInt(21) - 10);
        double standardDeviation = generator.nextDouble() * 5;
        try {
            return new NormalDistribution(expectedValue, standardDeviation);
        } catch (ParameterValidationFailException | NoSuchParameterException ex) {
            return null;
        }
    }
}
